-- Sergio Perez Camacho
-- El bar de Moe por Citas

with Ada.Text_Io ; use Ada.Text_Io;
with Ada.Integer_Text_Io; use Ada.Integer_Text_Io;
with Ada.Numerics.Float_Random ; use Ada.Numerics.Float_Random;

procedure MoeCitasAda is

   -- Constantes y demas ..

   MAX_CLIENTES: constant Integer:= 20;
   MAX_MAQUINAS: constant Integer:= 5;
   MAX_SILLAS: constant Integer:= 7;

   PROB_GANAR: constant Float:= 0.2; -- Probabilidad de ganar

   TIEMPO_BURNS: constant Float:= 5.0;
   TIEMPO_APAGADA: constant Float:= 3.0;

   PROB_BURNS_APAGA: constant Float:= 0.1;

   DINERO_INICIAL_MOE: constant Integer:= 10;

   DINERO_PARTIDA: constant Integer:= 1;

   MAX_DINERO_CLIENTES: constant Integer:=6;
   MIN_DINERO_CLIENTES: constant Integer:=3;

   PROB_CANSADO: constant Float:=0.1; -- Prob de que se canse de jugar si le queda dinero

   PROB_PEDIR_PRESTADO: constant Float:= 0.5;

   G : Generator;

   type TMaquinasBusy is array (1..MAX_MAQUINAS) of Boolean;

   type TJugadorMaquina is array (1..MAX_MAQUINAS) of Integer;

   task type Maquina is
      entry Init(Idd: in Integer);
      entry Apostar(Idd: in Integer; DineroApostado: in Integer; DineroGanado: in out Integer; OK: in out Boolean);
   end Maquina;

   type TMaquinas is array (1..MAX_MAQUINAS) of Maquina;

   Maquinas: TMaquinas;


   task Luz is
      entry Apagar;
      entry Encender;
      entry Estado(Est: in out Boolean);
   end Luz;

   task body Luz is
      Encendida: Boolean:= True;
   begin
      loop
         select
            accept Apagar do
               Encendida := False;
            end Apagar;
         or
            accept Encender do
               Encendida:= True;
            end Encender;
         or
            accept Estado(Est: in out Boolean) do
               Est:= Encendida;
            end Estado;
         or
            terminate;
         end select;
      end loop;
   end Luz;

   task body Maquina is

      HayLuz: Boolean := True;
      DineroGana: Integer;
      Id: Integer;
      JugadorActual: Integer := 0;


   begin
      accept Init(Idd: in Integer) do
         Id := Idd;
      end Init;
      loop
         select
            accept Apostar(Idd: in Integer; DineroApostado: in Integer; DineroGanado: in out Integer; OK: in out Boolean) do
 --              Put_Line("(*A*) El cliente " & Integer'Image(Idd) & " quiere apostar en la máquina " & Integer'Image(Id));
               Luz.Estado(HayLuz);
               DineroGana := 0;
               if (not HayLuz) then
                  OK:=False; -- Ops ... pobre Moe
               else
                  OK:= True;
                  if (Random(G) < PROB_GANAR) then
                     DineroGanado := DineroApostado * 3; -- Triplica, por ejemplo, las ganancias
                     Put_Line("[AA*G] El cliente " & Integer'Image(Idd) & " gana " & Integer'Image(DineroGanado) & " en la maquina " & Integer'Image(Id));
                  else
                     DineroGanado := 0;
                     Put_Line("[AA*P] El cliente " & Integer'Image(Idd) & " pierde en la maquina " & Integer'Image(Id));
                  end if; -- prob ganar
               end if; -- not hayluz
            end Apostar;
         or
            terminate;
         end select;
      end loop;
   end Maquina;

   task Burns;

   task body Burns is
   begin
      loop
         delay Duration(TIEMPO_BURNS);
         if(Random(G) < PROB_BURNS_APAGA) then
            Put_Line("(*BURNS*  APAGA   ) Mr. Burns tiene un mal momento y apaga la luz (**************************)");
            Luz.Apagar;
            exit;
--            delay Duration(TIEMPO_APAGADA);
--            Put_Line("(*BURNS* ENCIENDE ) Mr. Burns conecta de nuevo la luz");
         else
            Put_Line("(*BURNS*          ) Mr. Burns tiene un buen momento y no apaga la luz");
         end if;
      end loop;
--      Put_Line("(*BURNS*) Excelente... ya no quedan que quieran jugar ... me voy a la cama");
   end Burns;

   task Barra is
      entry Sentarse(Idd: in Integer);
      entry Levantarse(Idd: in Integer);
   end Barra;

   task body Barra is
      SillasLibres: Integer := MAX_SILLAS;
   begin
      loop
         select
            when (SillasLibres > 0) =>
               accept Sentarse(Idd: in Integer) do
                  Put_Line("[SSS] El cliente " & Integer'Image(Idd) & " se sienta en la barra");
                  SillasLibres:= SillasLibres - 1;
                  Put(SillasLibres,0);New_Line;
               end Sentarse;
         or
            accept Levantarse(Idd: in Integer) do
               Put_Line("[LLL] El cliente " & Integer'Image(Idd) & " se levanta de la barra ");
               SillasLibres:= SillasLibres + 1;
            end Levantarse;
         or
            terminate;
         end select;
      end loop;
   end Barra;


   task Moe is
      entry Jugar(Idd: in Integer; NMaquina: in out Integer; OK: in out Boolean);
      entry DejarDeJugar(Idd: in Integer; NMaquina: in Integer);
      entry Prestar(Idd: in Integer; DineroPrestamo: in Integer; OK: in out Boolean);
      entry DevolverPrestamo(Idd: in Integer; DineroPrestamo: in Integer);
      entry DevolverPartida(Idd: in Integer; DineroPartida: in Integer; OK: in out Boolean);
      entry QuienEnMaquina(NMaquina: in Integer; Idd: in out Integer); -- Devuelve quien juega en una maquina. 0 si nadie.
   end Moe;

   task body Moe is
      JugadorMaquina: TJugadorMaquina;
      MaquinasBusy: TMaquinasBusy := (others=>False);
      MaquinasLibres: Integer := MAX_MAQUINAS;
      Dinero: Integer := DINERO_INICIAL_MOE;
      HayLuz: Boolean:=True;

   begin
      loop
         select
            when MaquinasLibres > 0 =>
               accept Jugar(Idd: in Integer; NMaquina: in out Integer; OK: in out Boolean) do
                  Luz.Estado(HayLuz);
                  if HayLuz then
                     OK:= True;
                     for I in 1..MAX_MAQUINAS loop
                        if (not MaquinasBusy(I)) then
                           MaquinasBusy(I) := True;
                           NMaquina:= I;
                           JugadorMaquina(I):=Idd; -- Este jugador esta en esta maquina
                           exit;
                        end if;
                     end loop;
                     Put_Line("(*MOE* ASIGNA) Le dice al cliente " & Integer'Image(Idd) & " que vaya a la maquina " & Integer'Image(NMaquina));
                     MaquinasLibres := MaquinasLibres - 1;
                  else -- No hay luz
                     Put_Line("(*MOE* ASIGNA) Le dice al cliente " & Integer'Image(Idd) & " que no hay luz");
                     OK:= False;
                  end if;
               end Jugar;
         or
            accept DejarDeJugar(Idd: in Integer; NMaquina: in Integer) do
               MaquinasLibres := MaquinasLibres + 1;
               MaquinasBusy(NMaquina) := False;
               JugadorMaquina(NMaquina):=0; -- No hay jugador
               Put_Line("(*MOE* DEJAN MAQUINA) El cliente " & Integer'Image(Idd) & " que deja la maquina " & Integer'Image(NMaquina) & " - Maquinas: " & Integer'Image(MaquinasLibres) & "/" & Integer'Image(MAX_MAQUINAS));
            end DejarDeJugar;
         or
            accept Prestar(Idd: in Integer; DineroPrestamo: in Integer; OK: in out Boolean) do
               if (Dinero > DineroPrestamo) then
                  OK:= True;
                  Dinero:= Dinero - DineroPrestamo;
               else
                  Put_Line("(*MOE* PRESTA * NO) Le dice al cliente " & Integer'Image(Idd) & " que no tiene dinero para prestarle");
               end if;
            end Prestar;
         or
            accept DevolverPrestamo(Idd: in Integer; DineroPrestamo: in Integer) do
               if DineroPrestamo = 0 then
                  Put_Line("(*MOE* - ) Hace que el cliente " & Integer'Image(Idd) & " friegue los WCs <-------------------- OPS!");
               else
                  Dinero:= Dinero + DineroPrestamo;
                  Put_Line("(*MOE* +) Agradece al cliente " & Integer'Image(Idd) & " que de devuelva su dinero");
               end if;
            end DevolverPrestamo;
         or
            accept DevolverPartida(Idd: in Integer; DineroPartida: in Integer; OK: in out Boolean) do
               if (DineroPartida < Dinero) then
                  OK:=True;
                  Dinero:= Dinero - DineroPartida;
               else
                  OK:= False;
                  Put_Line("(*MOE*) oh oh! Moe no tiene dinero para devolver la partida a" & Integer'Image(Idd));
               end if;
            end DevolverPartida;
         or
            accept QuienEnMaquina(NMaquina: in Integer; Idd: in out Integer) do
               Idd:= JugadorMaquina(NMaquina);
            end QuienEnMaquina;
         or
            terminate;
         end select;
      end loop;
   end Moe;

   task type Cliente is
      entry Init(Idd: in Integer);
      entry Prestar(Idd: in Integer; DineroPrestamo: in Integer; OK: in out Boolean);
      entry DevolverPrestamo(Idd: in Integer; DineroPrestamo: in Integer);
   end Cliente;

   type TClientes is array (1..MAX_CLIENTES) of Cliente;

   Clientes: TClientes;



   task body Cliente is
      Id: Integer;
      DINERO: Integer;
      Mi_Maquina: Integer:=0;
      Cansado: Boolean:= False;
      Prestado_A: Integer:= 0;
      Prestado: Boolean:= False;
      IS_OK: Boolean:= False;
      HayLuz: Boolean := True;
      DineroGana: Integer:=0;
      DineroDebe: Integer:= 0;
      Debe_A: Integer :=0; -- 0 es Moe.. 1,2,3,4 ... los clientes

   begin
      accept Init(Idd: in Integer) do
         Id := Idd;
      end Init;
      DINERO:=Integer((Random(G)*Float(MAX_DINERO_CLIENTES-MIN_DINERO_CLIENTES)))+MIN_DINERO_CLIENTES;
      Put_Line("El cliente " & Integer'Image(Id) & " tiene inicialmente (dinero): " & Integer'Image(DINERO));
      delay 1.0;
      -- Llega al bar y quiere sentarse;
      Barra.Sentarse(Id);
      Moe.Jugar(Id, Mi_Maquina, IS_OK); -- Sentado, le pide jugar a Moe
      Barra.Levantarse(Id);      -- Se levanta y ...

      if IS_OK then -- HAY LUZ --

      -- Comienza a Jugar
      while (Dinero > 0 and not Cansado and HayLuz) loop
         --------------------- Procesamos las posibles peticiones ----------
         if (Prestar'Count > 0) then
            accept Prestar(Idd: in Integer; DineroPrestamo: in Integer; OK: in out Boolean) do
               if Prestado then
                  OK:= False;
               elsif DineroPrestamo > Dinero then
                  OK:= False;
               else
                  OK:= True;
                  Dinero:= Dinero - DineroPrestamo;
                  Prestado:= True;
                  Prestado_A:= Idd;
                  Put_Line("[CP**] El cliente " & Integer'Image(Id) & " le presta " & Integer'Image(DineroPrestamo) & " al cliente " & Integer'Image(Idd));
               end if;
            end Prestar;
         end if;
         -------------------------------------------------------------------
         -- Y ahora jugamos
         Dinero := Dinero - DINERO_PARTIDA;
         Put_Line("(*A*) El cliente " & Integer'Image(Id) & " juega una partida... (dinero:  " & Integer'Image(Dinero) &  " )");
         delay Duration(2.0*Random(G)+1.0);
         Maquinas(Mi_Maquina).Apostar(Id, DINERO_PARTIDA, DineroGana, HayLuz);
         if HayLuz then
            Dinero:= Dinero + DineroGana;
         end if;
         if (Random(G) < PROB_CANSADO) then
            Cansado:=True;
         end if; -- Cansado?

         if (Dinero = 0 and Random(G) < PROB_PEDIR_PRESTADO and DineroDebe = 0) then -- Si se cumple esto, puede intentar pedir prestado
            -- Pide prestado porque no tiene nada
            DineroDebe:=Integer((Random(G)*Float(MAX_DINERO_CLIENTES-MIN_DINERO_CLIENTES)))+MIN_DINERO_CLIENTES;
            Moe.Prestar(Id, DineroDebe, IS_OK);
            if not IS_OK then -- Moe no se lo presta
               Put_Line("(*A*) El cliente " & Integer'Image(Id) & " pide dinero -  " & Integer'Image(DineroDebe) &  " a Moe y NO se lo da");
               -- Intenta pedir a alguno de los que juegan
               for I in 1..MAX_MAQUINAS loop
                  Moe.QuienEnMaquina(I, Debe_A); --
                  if Debe_A /= 0 and Debe_A /= Id Then -- Hay alguien en esa maquina (y no es el mismo)
                     Clientes(Debe_A).Prestar(Id, DineroDebe, IS_OK);
                     if IS_OK then -- El cliente se lo presta
                        Put_Line("(*PC*) El cliente " & Integer'Image(Id) & " pide dinero  (" & Integer'Image(DineroDebe) &  ") al cliente " & Integer'Image(Debe_A) & " y SI se lo da");
                        exit; -- Dejamos de buscar dinero
                     else
                        Put_Line("(*PC*) El cliente " & Integer'Image(Id) & " pide dinero  (" & Integer'Image(DineroDebe) &  ") al cliente " & Integer'Image(Debe_A) & " y NO se lo da");
                        DineroDebe:=0;
                     end if;
                  end if;
               end loop;
               --
            else -- Se lo ha prestado
               Put_Line("[CPM] El cliente " & Integer'Image(Id) & " pide dinero (" & Integer'Image(DineroDebe) &  ") a Moe y SI se lo da");
               Debe_A:=0; -- Debe a Moe
               Dinero:=DineroDebe;
            end if;

         end if;

      end loop; -- while dinero ... luz ...


      Moe.DejarDeJugar(Id, Mi_Maquina); -- Deja la maquina

      -- El siguiente codigo podria provocar bloqueo, comentado

      --Si alguien me estaba pidiendo dinero, le digo que no
      --while Prestar'Count > 0 loop
      --   accept Prestar(Idd: in Integer; DineroPrestamo: in Integer; OK: in out Boolean) do
      --      OK:=False;
      --   end Prestar;
      --end loop;

      if Cansado then
         Put_Line("[ZZZ] El cliente se cansa de jugar" & Integer'Image(Id) & " y se marcha con (dinero): " & Integer'Image(DINERO));
      end if;

      if not HayLuz then
         Moe.DevolverPartida(Id, DINERO_PARTIDA, IS_OK);
         if IS_OK then
            Dinero:= Dinero + DINERO_PARTIDA;
            Put_Line("[DEV]+  El cliente " & Integer'Image(Id) & " recibe el dinero de la partida de Moe");
         else
            Put_Line("[DEV]- El cliente " & Integer'Image(Id) & " quema un poco de bar <--- [Se podria terminar aqui el programa, pero dejemos que se ejecute ;) ]");
         end if;
      end if;


      if ((DineroDebe > 0) and (Dinero >= DineroDebe)) then -- Si debe dinero y puede pagar
         if Debe_A = 0 then
            Moe.DevolverPrestamo(Id, DineroDebe);
         else
            Clientes(Debe_A).DevolverPrestamo(Id, DineroDebe);
         end if;
         Dinero := Dinero - DineroDebe;
      end if;

      if ((DineroDebe > 0) and (Dinero < DineroDebe)) then -- NO TIENE PARA PAGAR SUS DEUDAS
         if Debe_A = 0 then
            Moe.DevolverPrestamo(Id, 0);
         else
            Clientes(Debe_A).DevolverPrestamo(Id, 0);
         end if;
         Dinero := Dinero - DineroDebe;
      end if;



      if Prestado then
         Put_Line("[WAIT] El cliente " & Integer'Image(Id) & " espera a que le devuelva el dinero el cliente " & Integer'Image(Prestado_A));
         accept  DevolverPrestamo(Idd: in Integer; DineroPrestamo: in Integer) do
            if DineroPrestamo = 0 then
               Put_Line("[CPARTE] El cliente " & Integer'Image(Id) & " le parte las piernas a " & Integer'Image(Prestado_A) & " <--------------- OPS");
            else
               Put_Line("[CDEV]   El cliente " & Integer'Image(Id) & " recibe el dinero que le debia el cliente " & Integer'Image(Prestado_A));
            end if;
         end DevolverPrestamo;
      end if;

      Put_Line("·-=FIN CLIENTE=-· El cliente " & Integer'Image(Id) & " termina finalmente con (dinero): " & Integer'Image(Dinero) & "(Negativo es que ha dejado a deber)");

      else -- NO HAY LUZ
         Put_Line("OOOOOOH! El cliente " & Integer'Image(Id) & " se va porque NO HAY LUZ");
      end if;




   end Cliente;



begin
   Put_Line("************************************************");
   Put_Line(" Sergio Perez Camacho - El bar de Moe por Citas ");
   Put_Line("************************************************");
   New_Line;
   delay 1.5;
   New_Line;

   -- Inicializamos
   for I in 1..MAX_MAQUINAS loop
      Maquinas(I).Init(I);
   end loop;

   for I in 1..MAX_CLIENTES loop
      Clientes(I).Init(I);
   end loop;



end MoeCitasAda;

